/**
* @file
* This file is used to setup configuration file,
* send and receive data between networks,
* log messages to file, and encrypt passwords
*/
#ifndef UTILS_H
#define UTILS_H

//-------------------------------------------------------
// Header Files
//-------------------------------------------------------
#include <assert.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <errno.h>
#include <stdlib.h>
#include <stdbool.h> // for bool
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h> // For crypt()
#include "storage.h"
#include <time.h> // to set up time for logger() calls

//-------------------------------------------------------
// Constants
//-------------------------------------------------------
// comment character for configuration file
// Note: Can only be placed on a new line by itself and not after anything
static const char CONFIG_COMMENT_CHAR = '#';
#define MAX_CMD_LEN (1024 * 8) ///< maximum command's length given by client to server
#define LOG(x)  {printf x; fflush(stdout);} ///< printf x to stdout, flush without needing '\n' character
					    // @Note x will contain the braces as well ("...",...)
#ifdef NDEBUG
#define DBG(x)  {}
#else
#define DBG(x)  {printf x; fflush(stdout);}
#endif

#define MAX_MSG_LEN 1000 // Maximum message length for logger()
char message[MAX_MSG_LEN+1]; // for logger(), @note: Will segfault in sprintf() is

FILE *fileServer; // file to be used for logger() in server.c
FILE *fileClient; // file to be used for logger() in client.c and storage.c

/// A structure to store the configuration file's parameters
// Note: Does not store the table data currently
struct config_params
{
    char server_host[MAX_HOST_LEN];
    int server_port;
    char username[MAX_USERNAME_LEN];
    char password[MAX_ENC_PASSWORD_LEN];
    char data_directory[MAX_PATH_LEN]; // for Milestone 4
    int concurrency; 
};

/// A structure to check if there is duplicate or missing parameter names
struct check_duplmis
{
    int host, port, username, password, table, concurrency;
};

/* define a structure for the configuration information */
struct configuration
{
  char *host;
  int port;
  int concurrency; 
  char *username;
  char *password;
  struct table *tlist;
  char *data_dir;  
  int numTable;
  bool hostDone, portDone, usernameDone, passwordDone,tableDone, concurrencyDone;
  /* Note: Must have at least 1 table*/
  /* Note: Can only have 1 port, host, username, password */
};

/// An initializing function to set configs to 0
int initialCheck( struct check_duplmis *check);

// Tables
/* char **tables; // array of tables DONT THINK THIS IS USED ANYMORE*/
// @note Current number of tables is stored in check->table

/// Checks and parses the names of the table and key
int checkName(char *msg);
/// Checks and parses the value pertaining to a key and table
int checkValue(char *msg);

/// Gets a line from std in to parameter output up to max
int getLine(char* output, int max);

/// Returns number of digits number contains. EX: 1234 returns 4, 0 returns 1
int numDigits(int number);

//-------------------------------------------------------
// Utility Functions
//-------------------------------------------------------

// Network Protocol Sending functions

/**
* This function sends all data to sock
* @param sock The connfd to be sent to
* @param buf the buffer to store data to be sent
* @param len Maximum size of buf to send
*/
int sendall(const int sock, const char *buf, const size_t len);

/**
*  This function receives all data from sock
* @param sock The sockfd to be received from
* @param buf The buffer to store data received
* @param buflen Maximum size to be received
*/
int recvline(const int sock, char *buf, const size_t buflen);


/* @note This is the original used function prototype
*   int read_config(const char *config_file, struct config_params *params);
* This function reads the configuration file and processes it
* Note: This function depends on FLEX/BISON
* @return 0 if success, -1 otherwise
*/
int read_config(const char *config_file, struct config_params *params, struct check_duplmis *check);

/**
* This function processes one line from the config file
* and stores its value into config_params
* Called by process_config()
* @return -1 if unsuccessful
*/
int process_config_line(char *line, struct config_params *params, struct check_duplmis *check);

//-----------------------------------------------
// New Made Functions
//-----------------------------------------------

/// To log messages
void logger(FILE *file, char *message);

/// To close the logging files
void Fclose(int logger, int serverFile);

/// Prints out the user menu
void menu();

//-----------------------------
// Wrapper Functions
//-----------------------------

/// Creates a socket
int Socket(int family, int type, int protocol);
/// Accepts a peer socket
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
/// Binds a host socket to the server
void Bind(int fd, const struct sockaddr *sa, socklen_t salen);
/// This function connects to a host socket
int ConnectServ(int fd, const struct sockaddr *sa, socklen_t salen);
/// This function listens for peer sockets
void Listen(int fd, int backlog);
/// Closes the connection between client and server
void Close(int fd);
/// Gets the number of columns
int getCol(char* data);
///	Checks whether or not the column parameters are valid for setting
int validParam(char** config, char* arg, char** configType, int numCol);
///	Separates Param into name and type
void separateParam(char** colParam, char** colName, char** colType, int numCol);
/// Parses arguments from predicates
int getPred(char* original, char** column, char* oper, char** value);
///	Checks if the predicate name is a valid column
int validPredName(char** colName, char** colType, int numCol, char** predName, char** predValue, char* oper);
///	Parses name and value from databaes
void parseArg(char** colName, char* input, char** colValue, int numCol);
///	Trims whitespace before and after
char *trimwhitespace(char *str);
///	Gets arguments with + as delimiters
int getArgumentsJer(char** arg, char* cmd);
///	Gets all the keys from a specific table
int getAllKeys(char* tableName,char** keyOut,int max_keys);
///	Gets all the keys from a specific table
int getAllKeysCopiedNum(char* tableName,char** keyOut,int max_keys);
///	Checks if there are multiple preds with same col name
int checkPredDuplicate(char** predName, char* oper);

// @note: Must always define smallest element at top of header file,
//        similar to how you define function headers before main function

struct Columns
{
    char name[MAX_COLNAME_LEN + 1]; // Name of this column
    void *type; // The type of this column, has to be dynamically allocated and type cast
		 // you can know what type to typecast this pointer at run time using sizeof()
		  // string will require string size
		// e.g.:
		/*
			if (sizeof(type) == sizeof (int))
			{
				type = (int) parameterPassInForUpdateValue;
			{
			else if (sizeof(type) == sizeof(char * strsize))
			{
				type = (char *) paramaterPassInForUpdateValue; // not sure if this line is right for strings
			}
		*/
    int strsize; // has to be less than MAX_STRTYPE_SIZE, you can know this strsize from the types on the Table. 
		 // if types[x] = 15 => strsize is 5
		 // @note this is only useful if this column type is a string, else just initialize it to 0.
		// it is used to differentiate strings of different size, and for typecasting
};

/**
* Record represents each row in a table
*/
struct Record
{
    char key[MAX_KEY_LEN+1]; // This record's key (must be unique within a table)
    // Each record has multiple columns, each with their own values
    char value[MAX_VALUE_LEN+1]; // temporary so that M2 code compiles, needs to be removed
    int metaRecord;
    struct Columns values[MAX_COLUMNS_PER_TABLE]; // values of this record in an array of Column Structures
    struct Record *left; // left child
//    struct Record* right; // right child TODO: Change linked list to AVL tree after finishing M3
//    unsigned height; // height of this record node TODO: Used for AVL rotation algorithm after finishing M3
};


// Jerry Task 2 Begin

/*
* Data structure for each table in the server
* @note: All tables will be created after parsing in
*        configuration file at initialization of server
*/
struct Table
{
	   // you 'll have a total of MAX_TABLES and the data structure to store all these tables is below
	   // called sortedArrayTable, where you keep track of total number of tables as you pass in these values
    char name[MAX_TABLE_LEN+1]; // Name of this table
    struct Record* head; // points to the first record (row) in this table or NULL if table is empty
    char colNames[MAX_COLUMNS_PER_TABLE][MAX_COLNAME_LEN+1]; // an array of column names for to know columns that exists for this table
    int types[MAX_COLUMNS_PER_TABLE]; // an  array of column types to know the types f columns in this table
	/*
	   EX:
		 table marks studentName:char[30], studentMarks:int, studentGPA:long
		// First compare if this table name already exists in the sortedArrayTable, print error if it does
		numCols = 0; numRecs = 0;
		while (not end of this line, read the next thing (,) is the delimiter
		 (please make a getConfigArguments() function in utils.h that is similar to the current getArguments() function in utils.c)
		// Note: You will separate them into ["table marks studentNamechar[30]", "studentMarks:int",etc.]
		// store them into a temporary variable in a function called args[...], you figure out how long ... needs to be
		// please make another function called handleConfigArguments() that further separates these args[...] into:
				["studentName, "char", "30"] (for 1st iteration) and (second iteration) ["studentMarks", "int", NULL], etc.
		{
		    if (numCols >= 10)
		    {
			printf("Error: Too many columns for table %s in configuration file\n", name);
		    }
		    use handleConfigArguments to separate current args[numCols] to temp[3]
		    read column names and store into array of strings colNames above while keeping track of number of cols
				strcpy(colNames[numCols], temp[0]); // i assume you handle converting from args to temp properly
							 // and so temp[0] here is "studentName" for first iteration
		    read column type and handle each different cases in a switch:
		    case 1: char (you can't use case for strings so in code it has to be)
	       i.e. if (strcmp("char", temp[1] == 0) // case 1: char
			Read the size of the char
			    types[numCols] = (10 + SIZERead); // in this example it is 10 + 30, 10 is to differentiate char[1] with int below
		    case 2: int
			    types[numCols] = 1;  // 1 represents int
		    case 3: double
			    types[numCols] = 2; // 2 represents double
		    case 4: long
			    types[numCols] = 3; // 3 represents long
		    case 5: float
			    types[numCols] = 4; // 4 represents float
		    numCols++;
		}
	*/
    int numCols; // number of columns in this table
		 // @note make sure this is less than MAX_COLUMNS_PER_TABLE
    int numRecs; // number of Records for this table, initialized from 0.
		 // @note make sure less than MAX_RECORDS_PER_TABLE
};
// Jerry Task 2 End

static struct sortedArrayTable
{
    struct Table table[MAX_TABLES];
    int numTables; // has to be less than MAX_TABLES
} topTable;

// defined in utils.c
void initializeSortedArrayTable();
int insertTable(char* tname);
int sortTable();
void printTableName();
int findTableName(char* tname);
int insertRecord( struct Table *table, char* Key, char* Value);
int insertValueTok(char* Table, char* Key, char* Value, int token);
int updateRecordTok(char* name, char* value, int t_index, int token);
// Record functions
void printRecord(int t_index);
int deleteRecord(char* name, int t_index);
//int insertValue(char* Table, char* Key, char* Value);
int insertValue(char* Table, char* Key, char* Value, int encrypt); 
int updateRecord(char* name, char* value, int t_index);
void getRecord(char* Table, char* Key, char* Value, int t_index, int* tokenOut);
int getValue(char* Table, char* Key, char* Value, int* tokenOut);
struct Record* createRecord(char* name, char* value);
int findTableIndex(char* tname);
int findKeys(char** predName, char** predValue, char* oper, char* tableName, char** keyOut, int max_keys, int numCol);

process (char *deckfname, char *msg, char * newmsg,  char mode); 


// TODO: For milestone 3, EACH TABLE CAN HAVE MULTIPLE RECORDS
// Extended storage server constants.

//----------------------------------
// Encryption
//----------------------------------
#define DEFAULT_CRYPT_SALT "xx"
char *generate_encrypted_password(const char *passwd, const char *salt);

int config_parser();
#endif
